package com.niodata.dp.core.security;

import com.niodata.dp.core.OwnerType;
import com.niodata.dp.core.resource.CombinedResourceDetailResolver;
import com.niodata.dp.core.resource.Resource;
import com.niodata.dp.core.resource.ResourceDetail;
import com.niodata.dp.core.resource.ResourceManager;
import com.niodata.dp.core.service.SystemService;
import com.niodata.dp.core.usermgr.enums.GroupRole;
import com.niodata.dp.core.usermgr.model.UserGroup;
import com.niodata.dp.core.usermgr.model.UserInfo;
import com.niodata.dp.core.usermgr.op.GroupInfoService;
import com.niodata.dp.core.usermgr.op.UserService;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("DefaultAccessControl")
public class DefaultOperationAccessControl implements OperationAccessControl {

  private static final Logger logger = Logger.getLogger(DefaultOperationAccessControl.class);
  @Autowired
  @Qualifier("DpUserMgrGroup")
  private GroupInfoService groupOperation;
  @Autowired
  @Qualifier("DpUserMgrUser")
  private UserService userOperation;
  @Autowired
  @Qualifier("DpUserMgrGroup")
  private GroupInfoService groupInfoService;

  @Autowired
  @Qualifier("ResourceSecurityManagerImpl")
  private ResourceSecurityManager resourceSecurityManager;
  @Autowired
  @Qualifier("ResourceManagerImpl")
  private ResourceManager resourceManager;
  @Autowired
  @Qualifier("ServiceSecurityManagerImpl")
  private ServiceSecurityManager serviceSecurityManager;
  @Autowired
  private CombinedResourceDetailResolver resourceDetailResolver;

  @Override
  public void checkPermission(ResourceDetail resourceDetail, UserInfo userInfo, String accessType)
        throws AccessDeniedException {
    if (resourceDetail == null) {
      logger.error("resource detail is null");
      throw new AccessDeniedException("no resource found", null);
    }
    if (isOwnerOrMaster(resourceDetail, userInfo)) {
      return;
    }

    List<ResourceAuth> auths = this.resourceSecurityManager.getUserAuthorizedRes(
          userInfo.getUserId(),
          resourceDetail.getResourceType());
    boolean hasAuth = false;
    String[] parentOperations =
          resourceDetail.getResourceType().getHierarchyParentOperations(accessType);
    ResourceAuth authResult = null;
    for (ResourceAuth auth : auths) {
      if (auth.getResourceId().equals(resourceDetail.getId())) {
        if (auth.getAccessType().equals(accessType)) {
          hasAuth = true;
          break;
        } else {
          for (String op : parentOperations) {
            if (op.equals(auth.getAccessType())) {
              hasAuth = true;
              authResult = auth;
              break;
            }
          }
        }
      }
    }

    if (authResult != null) {
      if (authResult.getTargetType().equals(OwnerType.GROUP.name())) {
        boolean isMaster =
              this.groupInfoService.isMasterOrOwner(userInfo.getUserId(), authResult.getTargetId());
        hasAuth = isMaster;
      } else {
        hasAuth = authResult.getTargetId() == userInfo.getUserId();
      }
    }
    if (!hasAuth) {
      throw new AccessDeniedException(resourceDetail.getPath(), userInfo.getUserId(), accessType);
    }


  }

  @Override
  public void checkPermission(Resource resource, UserInfo userInfo, String accessType)
        throws AccessDeniedException {
    ResourceDetail detail = this.resourceDetailResolver.resolve(resource);
    if (detail == null) {
      logger.error("resource not found " + resource.content());
      return;
    }

    this.checkPermission(detail, userInfo, accessType);
  }


  @Override
  public void checkServicePermission(long userId, long groupId, SystemService service)
        throws AccessDeniedException {
    boolean granted = serviceSecurityManager.isGroupGrantedService(groupId, service);
    if (!granted) {
      logger.error("group not granted service");
      throw new AccessDeniedException(
            String.format("service %s not granted for group %d", service.getName(), groupId), null);
    }
    if (!groupInfoService.isMasterOrOwner(userId, groupId)) {
      logger.error("user is not master or owner");
      throw new AccessDeniedException("user is not master or owner of group " + groupId, null);
    }
  }

  @Override
  public void checkServicePermission(long userId, SystemService service)
        throws AccessDeniedException {
    boolean granted = serviceSecurityManager.isUserGrantedService(userId, service);
    if (!granted) {

      throw new AccessDeniedException(
            String.format("service %s not granted for user %d", service.getName(), userId), null);
    }

  }

  @Override
  public void checkServicePermission(long userId, OwnerType ownerType, long ownerId,
        SystemService service) throws AccessDeniedException {
    if (ownerType == OwnerType.PUBLIC) {
      return;
    } else if (ownerType == OwnerType.USER) {
      checkServicePermission(userId, service);
    } else {
      checkServicePermission(userId, ownerId, service);
    }
  }

  @Override
  public List<String> getUserServicePermissions(long userId) {
    List<UserGroup> userGroupList = groupInfoService.getUserGroups(userId);
    List<Long> ids = userGroupList.stream().filter(userGroup -> {
      return userGroup.getRole().equals(GroupRole.MASTER) || userGroup.getRole()
            .equals(GroupRole.OWNER);
    }).map(UserGroup::getGroupId).collect(Collectors.toList());
    List<SystemService> systemServices = new ArrayList<>();
    systemServices.addAll(serviceSecurityManager.getUserGrantedServices(userId));
    ids.forEach(id -> {
      systemServices.addAll(serviceSecurityManager.getGroupGrantedServices(id));
    });
    List<String> services = systemServices.stream().map(systemService -> {
      return systemService.getName();
    }).distinct().collect(Collectors.toList());
    return services;
  }

  private boolean isOwnerOrMaster(ResourceDetail resourceDetail, UserInfo userInfo) {
    if (resourceDetail.getOwnerType().equals(OwnerType.USER)) {
      if (resourceDetail.getOwnerId() == userInfo.getUserId()) {
        return true;
      }
    }

    if (resourceDetail.getOwnerType().equals(OwnerType.GROUP)) {
      UserGroup userGroup =
            this.groupOperation.getUserGroupInfo(userInfo.getUserId(), resourceDetail.getOwnerId());
      return userGroup != null && (userGroup.getRole().equals(GroupRole.MASTER)
            || userGroup.getRole().equals(GroupRole.OWNER));
    }
    return false;
  }

  public GroupInfoService getGroupOperation() {
    return groupOperation;
  }

  public void setGroupOperation(GroupInfoService groupOperation) {
    this.groupOperation = groupOperation;
  }

  public CombinedResourceDetailResolver getResourceDetailResolver() {
    return resourceDetailResolver;
  }

  public void setResourceDetailResolver(CombinedResourceDetailResolver resourceDetailResolver) {
    this.resourceDetailResolver = resourceDetailResolver;
  }

}
